Skip to content

fix(security): 2 improvements across 2 files#228

Open
tomaioo wants to merge 2 commits intoOpenCoworkAI:mainfrom
tomaioo:fix/security/toctou-race-in-defensive-file-reader-can
Open

fix(security): 2 improvements across 2 files#228
tomaioo wants to merge 2 commits intoOpenCoworkAI:mainfrom
tomaioo:fix/security/toctou-race-in-defensive-file-reader-can

Conversation

@tomaioo
Copy link
Copy Markdown

@tomaioo tomaioo commented Apr 24, 2026

Summary

fix(security): 2 improvements across 2 files

Problem

Severity: Medium | File: apps/desktop/src/main/imports/safe-read.ts:L34

safeReadImportFile performs stat(path) to validate file type and size, then separately calls readFile(path). An attacker with local write access to the same filesystem location can race-replace the file between these calls (e.g., swap a small regular file for a FIFO/device/very large file), bypassing the earlier validation and potentially causing hangs or memory pressure in the main process.

Solution

Open the file once and validate/read via the same file descriptor to avoid path re-resolution races. For example: const fh = await open(path, O_RDONLY | O_NOFOLLOW) (where supported), then const st = await fh.stat(), enforce st.isFile() and size cap, then await fh.readFile('utf8'), finally close handle in finally.

Changes

  • apps/desktop/src/main/imports/safe-read.ts (modified)
  • apps/desktop/src/main/exporter-ipc.ts (modified)

tomaioo added 2 commits April 24, 2026 11:18
- Security: TOCTOU race in defensive file reader can bypass type/size checks
- Security: Unbounded IPC payload size for export can enable renderer-to-main DoS

Signed-off-by: tomaioo <203048277+tomaioo@users.noreply.github.com>
- Security: TOCTOU race in defensive file reader can bypass type/size checks
- Security: Unbounded IPC payload size for export can enable renderer-to-main DoS

Signed-off-by: tomaioo <203048277+tomaioo@users.noreply.github.com>
@github-actions github-actions Bot added the area:desktop apps/desktop (Electron shell, renderer) label Apr 24, 2026
Copy link
Copy Markdown
Contributor

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Findings

  • [Blocker] open() can hang on FIFOs before the new regular-file check runs — safeReadImportFile() now calls open(path, 'r') before fileHandle.stat(), so a path swapped to a named pipe will block the Electron main process instead of returning null. I reproduced this on the Linux runner with mkfifo plus fs.promises.open(), which timed out after 2s. Evidence: apps/desktop/src/main/imports/safe-read.ts:32.
    Suggested fix:
    import { constants } from 'node:fs';
    import { open } from 'node:fs/promises';
    
    const flags = constants.O_RDONLY | (constants.O_NONBLOCK ?? 0);
    fileHandle = await open(path, flags);
    This keeps the single-descriptor flow that fixes the TOCTOU issue, but avoids blocking on FIFO/socket-style paths before fileHandle.stat() can reject them.

Summary

  • Review mode: initial
  • 1 blocker found in the new safeReadImportFile() flow. The TOCTOU fix is directionally right, but the current open(path, 'r') ordering reintroduces a main-process DoS path for named pipes at apps/desktop/src/main/imports/safe-read.ts:32.

Testing

  • Not run (automation). Suggested regression tests: add a POSIX-only safe-read case proving a FIFO path returns null without blocking; add a parseRequest() case for oversized htmlContent.

open-codesign Bot

let fileHandle: Awaited<ReturnType<typeof open>>;
try {
stats = await stat(path);
fileHandle = await open(path, 'r');
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

open(path, 'r') runs before the regular-file check, so a contributor can replace this path with a FIFO and block the main process before fileHandle.stat() executes. On this Linux runner, mkfifo + fs.promises.open() hung until timeout killed it.

Suggested fix:

import { constants } from 'node:fs';
import { open } from 'node:fs/promises';

const flags = constants.O_RDONLY | (constants.O_NONBLOCK ?? 0);
fileHandle = await open(path, flags);

That preserves the single-handle TOCTOU fix while preventing FIFO/socket-style paths from hanging the process.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:desktop apps/desktop (Electron shell, renderer)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant